Dart 2.7 发布: 更安全、更具表现力的 Dart
上周,我们发布了 Dart 2.7 SDK 的稳定版本,它可以为开发者提供多项新功能。Dart 语言经过了充实的一年,它是一种针对客户端优化的语言,适用于在任何平台上开发高效运行的应用。我们今年发布了 6 个新版本,数十项新功能。我们很欣喜地看到这些功能已经被 Dart 社区广泛使用。最近的 GitHub Octoverse 显示,根据多个参与方的评估结果,Dart 被认定为增长速度最快的编程语言 (排名第一),这一消息让我们备受鼓舞。
Dart 2.7 增加了对扩展方法的支持,此外还添加了一个新的代码包,用来处理带有特殊字符的字符串。我们更新了空安全 (已经实现类型安全的可空和非空类型),还通过 DartPad 带来了全新的代码体验环境 (而且支持空安全)。在生态系统层级,pub.dev 现在加入了新的点赞 (Like) 功能,用户们喜欢代码包如今更加一目了然。Dart 2.7 现在就可以从 dart.dev 下载并作为 SDK 使用,并且它也包含在发布的 Flutter 1.12 中。
GitHub Octoverse
https://octoverse.github.com/
增长速度最快的编程语言
https://octoverse.github.com/#top-languages
下载 Dart 2.7
http://dart.dev/
扩展方法
extension ParseNumbers on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
main() {
int i = '42'.parseInt();
print(i);
}
扩展方法是静态的
dynamic d = '2';
d.parseInt();
→ Runtime exception: NoSuchMethodError
var v = '1';
v.parseInt(); // Works!
类型推断 https://dart.dev/guides/language/sound-dart#type-inference
扩展可以拥有类型变量
extension FancyList<T> on List<T> {
List<T> get evenElements {
return <T>[for (int i = 0; i < this.length; i += 2) this[i]];
}
}
我们把这个功能称作 "扩展方法" 是因为,如果您在其他编程语言中使用过相应的语言功能,就会对这个术语感到熟悉。不过在 Dart 中,这个功能更加宽泛: 它还支持使用新的 getter、setter 以及运算符来扩展类。在上面那个 FancyList 的例子中,evenElements 就是一个 getter。下面则是一个例子,用来展示如何为 String 添加一个用于字符串移位的运算符:
extension ShiftString on String {
String operator <<(int shift) {
return this.substring(shift, this.length) + this.substring(0, shift);
}
}
来自社区的优秀范例
// Create a Duration via a `minutes` extension on num.
Duration tenMinutes = 10.minutes;
// Create a Duration via an `hours` extension on num.
Duration oneHourThirtyMinutes = 1.5.hours;
// Create a DateTime using a `+` operator extension on DateTime.
final DateTime afterTenMinutes = DateTime.now() + 10.minutes;
Marcelo Glasberg 创建了 i18n (国际化) 代码包,它使用扩展方法来简化字符串的本地化操作:
Text('Hello'.i18n) // Displays Hello in English, Hola in Spanish, etc.
Simon Leier 创建了 dartx 代码包,其中包含了多个核心 Dart 类型的扩展,如:
var allButFirstAndLast = list.slice(1, -2); // [1, 2, 3, 4]
var notBlank = ' .'.isBlank; // false
var file = File('some/path/testFile.dart');
print(file.name); // testFile.dart
print(file.nameWithoutExtension); // testFile
time 代码包 https://pub.dev/packages/time i18n (国际化) 代码包 https://www.reddit.com/r/FlutterDev/comments/dm288s/dart_extensions_applied_to_i18n_you_have/ dartx 代码包 https://pub.dev/packages/dartx
更安全的字符串截取操作
UTF-16 编码 https://en.wikipedia.org/wiki/UTF-16
var input = ['Resume'];
input.forEach((s) => print(s.substring(0, 3)));
$ dart main.dart
Res
// New longer input list:
var input = ['Resume', 'Résumé', '이력서', '💼📃', 'Currículo'];
$ dart main.dart
Res
Ré
이력서
💼�
Cur
那问题来了。有些字符串处理正常,但是 Résumé 和 💼📃 这些 "特殊" 字符串呢?先来看 Résumé,为什么我们的结果字符串里只有两个字符?再看看 💼📃,这个奇怪的问号又是怎么回事?这里的问题涉及到 Unicode 中的一些不为人知的秘密。Résumé 中的字符 é 其实会占据两个编码位: 一个 e,还有一个 "重音组合符" 。而纸卷图标 📃,确实只占据一个编码位,但这个编码却是用 U+d83d 和 U+dcc3 代理对 (surrogate pair) 来编码的。是不是被搞迷糊了?
Unicode 中的一些不为人知的秘密 https://eev.ee/blog/2015/09/12/dark-corners-of-unicode/ 重音组合符 https://unicode.org/cldr/utility/character.jsp?a=0301 纸卷图标 emoji https://emojipedia.org/emoji/%F0%9F%93%83/
// Before:
input.forEach((s) => print(s.substring(0, 3)));
// After, using the characters package:
input.forEach((s) => print(s.characters.take(3)));
首先,我们要使用便捷的.characters 扩展方法,从文本中的 String 里创建出新的 Characters 实例。然后我们就可以使用 take() 方法提取出前 3 个字符。
这个新代码包的技术预览版已经在 pub.dev 上发布。很期待听到大家对这个代码包的反馈。如果您发现了其中的任何问题,请随时告知我们。
Unicode 字形群集 https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries characters 代码包 https://pub.dev/packages/characters 提交反馈 https://github.com/dart-lang/characters/issues
空安全预览
void main() {
Person('Larry', birthday: DateTime(1973, 03, 26)).describe();
Person('Sergey').describe();
}
class Person {
String firstName;
DateTime birthday;
Person(this.firstName, {this.birthday});
void describe() {
print(firstName);
int birthyear = birthday?.year;
print('Born ${DateTime.now().year - birthyear} years ago');
}
}
即将在 Dart 中支持空安全 https://medium.com/dartlang/announcing-dart-2-5-super-charged-development-328822024970#0391
如果我们运行这段代码,它就会在运行第二个 Person 的 describe 方法时崩溃,并抛出一个空指针异常。因为这个人没有设定生日。我们在编程时犯了一个错误: 虽然我们已经预料到有些人的生日是未知的,在构造方法里中将 "生日" 设为可选,并在 birthday?.year 中有测试空生日,但我们忘了去处理 birthyear 也为空的情况。
现在我们把这段代码粘贴进我们新推出的空安全代码体验环境,它是 DartPad 的一个特殊版本,其中包含静态分析功能 (是空安全功能的子集) 的技术预览。甚至都不需要运行代码,我们就可以看到 3 个问题:
声明 birthday 可空,将 DateTime birthday 修改为 DateTime? birthday
声明当 birthday 为空时 birthyear 可空,将 int birthyear 修改为 int? birthyear
将第二个 print 调用放进空测试中:
if (birthyear != null) {...}
空安全代码体验环境 https://nullsafety.dartpad.dev 最终得到空安全代码 https://gist.github.com/mit-mit/c210bfb088545e69ba9231ee459615ba
希望这个例子可以让您明白我们想使用空安全功能带来何种体验。如前所示,这个体验环境只是空安全功能中的一部分的早期技术预览,开发工作仍在进行。我们正在努力在 Dart SDK 中提供空安全功能的第一个 beta 版本。以下是我们准备在 beta 版中推出的内容:
可空和非空引用的完整实现 将空安全整合至 Dart 的类型推断和 smart promotion (例如,允许在分配或空检查后安全访问可空变量)
修改 Dart 核心代码库,使之声明可空和非可空类型
添加迁移工具,这个工具可以自动完成大部分的代码升级操作,协助开发者升级 Dart 应用和代码包
核心代码库 https://dart.dev/guides/libraries
此项工作完成后,我们会在 beta 版 SDK 中发布它,供大家在自己的应用和代码包中使用。我们还准备在新功能实现后对空安全体验环境进行更新。
我们知道很多开发者都想尽快用上空安全功能,大家可以在自己方便的时候展开代码迁移工作,在做好准备之后再使用这项功能。尚未采用这项功能的代码库和代码包将可以依赖那些已经采用这项功能的代码库,反之亦然。
在今后的几个月中,我们还会带来更多关于空安全的消息,比如说,我们会提出更加详细的建议,引导大家为迁移做准备。
在 pub.dev 上为代码包点赞
我们还在 pub.dev 上发布了 "为代码包点赞" 功能,方便大家 "亲手" 表明自己对代码包的喜爱。如果您想要为一个代码包点赞,只需点击代码包详情信息旁边的大拇指图标即可。
△ pub.dev 代码包详情页增加了点赞按钮
谢谢大家
我们代表 Dart 团队感谢大家,感谢 Dart 社区的所有成员,谢谢您们持续不断的支持!请继续向我们提供反馈,并继续参与 Dart 相关讨论,继续融入 Dart 社区。很显然,没有 Dart 社区的支持,我们不可能完成这个优异的开源项目。
对 Dart 来说,2019 年是激动人心的一年,但我们并不会就此止步。我们为 2020 年制定了雄心勃勃的计划。我们准备发布一些功能的稳定版本,这些功能包括 dart:ffi、空安全,以及其他全新功能。请大家开始使用 Dart 2.7,大家可以前往 dart.dev 下载,另外最新发布的 Flutter 1.12 中也包含它,最近刚刚经过重新设计的 DartPad 中也包含 Dart 2.7。
参与讨论,融入社区 https://dart.dev/community Dart:ffi https://dart.dev/guides/libraries/c-interop 空安全 https://github.com/dart-lang/language/issues/110 前往 dart.dev 下载 http://dart.dev 重新设计的 DartPad https://medium.com/dartlang/a-brand-new-dartpad-dev-with-flutter-support-16fe6027784
Flutter 开发者社区中文资源: https://flutter.cn
想了解更多 Flutter 内容?
在公众号首页发送关键词 "Flutter",获取相关历史技术文章;
还有更多疑惑?欢迎点击菜单 "联系我们" 反馈您在开发过程中遇到的问题。
推荐阅读